import os
import shutil
import hashlib
from typing import List, Dict, Any, Optional, BinaryIO, Union
from datetime import datetime
from pathlib import Path
import json
import tempfile
from io import BytesIO

from ..base_client import (
    BaseCloudClient, LoginCredentials, PersistentLoginInfo, 
    AccountInfo, FileInfo, TrashItem
)


class LocalStorageClient(BaseCloudClient):
    """本地文件系统适配器，用于测试和演示云盘统一接口"""
    
    def __init__(self, provider: str):
        super().__init__(provider)
        self.root_path: Optional[Path] = None
        self.trash_path: Optional[Path] = None
        self.username: Optional[str] = None
    
    def _ensure_paths(self):
        """确保根目录和回收站目录存在"""
        if self.root_path:
            self.root_path.mkdir(parents=True, exist_ok=True)
            self.trash_path.mkdir(parents=True, exist_ok=True)
    
    def _get_full_path(self, remote_path: str) -> Path:
        """获取完整的本地路径"""
        if not self.root_path:
            raise ValueError("Not authenticated")
        
        # 规范化路径
        remote_path = remote_path.strip('/')
        if not remote_path:
            return self.root_path
        
        return self.root_path / remote_path
    
    def _get_trash_path(self, original_path: str) -> Path:
        """获取回收站中的路径"""
        if not self.trash_path:
            raise ValueError("Not authenticated")
        
        # 使用原始路径的hash作为回收站中的文件名，避免冲突
        path_hash = hashlib.md5(original_path.encode()).hexdigest()
        return self.trash_path / f"{path_hash}_{Path(original_path).name}"
    
    def _calculate_md5(self, file_path: Path) -> str:
        """计算文件MD5"""
        if not file_path.exists() or file_path.is_dir():
            return ""
        
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()
    
    def _get_dir_size(self, path: Path) -> int:
        """计算目录大小"""
        total_size = 0
        try:
            for dirpath, dirnames, filenames in os.walk(path):
                for filename in filenames:
                    filepath = Path(dirpath) / filename
                    try:
                        total_size += filepath.stat().st_size
                    except (OSError, IOError):
                        continue
        except (OSError, IOError):
            pass
        return total_size
    
    # ==============================
    # 登录认证接口实现
    # ==============================
    
    def login(self, credentials: LoginCredentials) -> PersistentLoginInfo:
        """本地存储登录（创建用户目录）"""
        if not credentials.username:
            raise ValueError("Username is required for local storage")
        
        self.username = credentials.username
        
        # 创建用户专属目录
        base_path = Path(tempfile.gettempdir()) / "clouds_sdk_local" / credentials.username
        self.root_path = base_path / "files"
        self.trash_path = base_path / "trash"
        
        self._ensure_paths()
        
        self.is_authenticated = True
        
        # 创建持久化信息
        persistent_info = PersistentLoginInfo(
            provider=self.provider,
            token=f"local_token_{self.username}",
            extra_data={
                "root_path": str(self.root_path),
                "trash_path": str(self.trash_path),
                "username": self.username
            }
        )
        
        self.persistent_info = persistent_info
        return persistent_info
    
    def login_with_persistent_info(self, persistent_info: PersistentLoginInfo) -> bool:
        """使用持久化信息登录"""
        try:
            if not persistent_info.extra_data:
                return False
            
            self.username = persistent_info.extra_data.get("username")
            root_path_str = persistent_info.extra_data.get("root_path")
            trash_path_str = persistent_info.extra_data.get("trash_path")
            
            if not all([self.username, root_path_str, trash_path_str]):
                return False
            
            self.root_path = Path(root_path_str)
            self.trash_path = Path(trash_path_str)
            
            # 检查目录是否存在
            if not self.root_path.exists():
                return False
            
            self._ensure_paths()
            self.is_authenticated = True
            self.persistent_info = persistent_info
            return True
            
        except Exception:
            return False
    
    def refresh_login(self) -> Optional[PersistentLoginInfo]:
        """刷新登录状态（本地存储不需要刷新）"""
        if self.is_authenticated and self.persistent_info:
            return self.persistent_info
        return None
    
    def logout(self) -> bool:
        """登出"""
        self.is_authenticated = False
        self.persistent_info = None
        self.root_path = None
        self.trash_path = None
        self.username = None
        return True
    
    def is_login_valid(self) -> bool:
        """检查登录状态"""
        return (self.is_authenticated and 
                self.root_path is not None and 
                self.root_path.exists())
    
    # ==============================
    # 文件管理接口实现
    # ==============================
    
    def upload_file(self, file_stream: Union[bytes, BinaryIO], remote_path: str, **kwargs) -> bool:
        """上传文件"""
        try:
            full_path = self._get_full_path(remote_path)
            
            # 确保父目录存在
            full_path.parent.mkdir(parents=True, exist_ok=True)
            
            # 写入文件
            if isinstance(file_stream, bytes):
                with open(full_path, 'wb') as f:
                    f.write(file_stream)
            else:
                with open(full_path, 'wb') as f:
                    shutil.copyfileobj(file_stream, f)
            
            return True
            
        except Exception:
            return False
    
    def download_file_stream(self, remote_path: str) -> BinaryIO:
        """下载文件流"""
        full_path = self._get_full_path(remote_path)
        
        if not full_path.exists() or full_path.is_dir():
            raise FileNotFoundError(f"File not found: {remote_path}")
        
        return open(full_path, 'rb')
    
    def get_download_url(self, remote_path: str, expires_in: int = 3600) -> str:
        """获取下载链接（本地文件返回file://协议）"""
        full_path = self._get_full_path(remote_path)
        
        if not full_path.exists():
            raise FileNotFoundError(f"File not found: {remote_path}")
        
        return f"file://{full_path.absolute()}"
    
    def list_files(self, remote_path: str = "/") -> List[FileInfo]:
        """列出目录下的文件"""
        full_path = self._get_full_path(remote_path)
        
        if not full_path.exists():
            raise FileNotFoundError(f"Directory not found: {remote_path}")
        
        if not full_path.is_dir():
            raise NotADirectoryError(f"Not a directory: {remote_path}")
        
        files = []
        try:
            for item in full_path.iterdir():
                stat = item.stat()
                
                file_info = FileInfo(
                    name=item.name,
                    path=f"{remote_path.rstrip('/')}/{item.name}",
                    is_dir=item.is_dir(),
                    size=stat.st_size if item.is_file() else None,
                    md5=self._calculate_md5(item) if item.is_file() else None,
                    created_at=datetime.fromtimestamp(stat.st_ctime),
                    updated_at=datetime.fromtimestamp(stat.st_mtime)
                )
                files.append(file_info)
        except OSError:
            pass
        
        return files
    
    def get_file_info(self, remote_path: str) -> FileInfo:
        """获取文件信息"""
        full_path = self._get_full_path(remote_path)
        
        if not full_path.exists():
            raise FileNotFoundError(f"File not found: {remote_path}")
        
        stat = full_path.stat()
        
        return FileInfo(
            name=full_path.name,
            path=remote_path,
            is_dir=full_path.is_dir(),
            size=stat.st_size if full_path.is_file() else None,
            md5=self._calculate_md5(full_path) if full_path.is_file() else None,
            created_at=datetime.fromtimestamp(stat.st_ctime),
            updated_at=datetime.fromtimestamp(stat.st_mtime)
        )
    
    def create_folder(self, remote_path: str) -> bool:
        """创建文件夹"""
        try:
            full_path = self._get_full_path(remote_path)
            full_path.mkdir(parents=True, exist_ok=True)
            return True
        except Exception:
            return False
    
    def delete_file(self, remote_path: str, permanent: bool = False) -> bool:
        """删除文件或文件夹"""
        try:
            full_path = self._get_full_path(remote_path)
            
            if not full_path.exists():
                return False
            
            if permanent:
                # 永久删除
                if full_path.is_dir():
                    shutil.rmtree(full_path)
                else:
                    full_path.unlink()
            else:
                # 移动到回收站
                trash_path = self._get_trash_path(remote_path)
                
                # 保存原始路径信息
                info_file = trash_path.with_suffix('.info')
                info_data = {
                    'original_path': remote_path,
                    'deleted_at': datetime.now().isoformat(),
                    'is_dir': full_path.is_dir()
                }
                
                with open(info_file, 'w') as f:
                    json.dump(info_data, f)
                
                # 移动文件/目录
                if full_path.is_dir():
                    shutil.move(str(full_path), str(trash_path))
                else:
                    shutil.move(str(full_path), str(trash_path))
            
            return True
            
        except Exception:
            return False
    
    def move_file(self, src_path: str, dst_path: str) -> bool:
        """移动文件或文件夹"""
        try:
            src_full = self._get_full_path(src_path)
            dst_full = self._get_full_path(dst_path)
            
            if not src_full.exists():
                return False
            
            # 确保目标目录存在
            dst_full.parent.mkdir(parents=True, exist_ok=True)
            
            shutil.move(str(src_full), str(dst_full))
            return True
            
        except Exception:
            return False
    
    def copy_file(self, src_path: str, dst_path: str) -> bool:
        """复制文件或文件夹"""
        try:
            src_full = self._get_full_path(src_path)
            dst_full = self._get_full_path(dst_path)
            
            if not src_full.exists():
                return False
            
            # 确保目标目录存在
            dst_full.parent.mkdir(parents=True, exist_ok=True)
            
            if src_full.is_dir():
                shutil.copytree(str(src_full), str(dst_full))
            else:
                shutil.copy2(str(src_full), str(dst_full))
            
            return True
            
        except Exception:
            return False
    
    def rename_file(self, remote_path: str, new_name: str) -> bool:
        """重命名文件或文件夹"""
        try:
            full_path = self._get_full_path(remote_path)
            
            if not full_path.exists():
                return False
            
            new_path = full_path.parent / new_name
            full_path.rename(new_path)
            return True
            
        except Exception:
            return False
    
    def file_exists(self, remote_path: str) -> bool:
        """检查文件是否存在"""
        try:
            full_path = self._get_full_path(remote_path)
            return full_path.exists()
        except Exception:
            return False
    
    # ==============================
    # 账号信息接口实现
    # ==============================
    
    def get_account_info(self) -> Optional[AccountInfo]:
        """获取账号信息"""
        if not self.is_authenticated or not self.root_path:
            return None
        
        try:
            # 计算已用空间
            used_space = self._get_dir_size(self.root_path)
            
            # 获取磁盘空间信息
            stat = shutil.disk_usage(self.root_path)
            total_space = stat.total
            available_space = stat.free
            
            return AccountInfo(
                username=self.username or "local_user",
                total_space=total_space,
                used_space=used_space,
                available_space=available_space,
                extra_info={
                    "root_path": str(self.root_path),
                    "provider": "local_storage"
                }
            )
            
        except Exception:
            return None
    
    # ==============================
    # 回收站接口实现
    # ==============================
    
    def list_trash(self) -> List[TrashItem]:
        """列出回收站内容"""
        if not self.trash_path or not self.trash_path.exists():
            return []
        
        trash_items = []
        try:
            for info_file in self.trash_path.glob("*.info"):
                try:
                    with open(info_file, 'r') as f:
                        info_data = json.load(f)
                    
                    # 获取对应的文件/目录
                    trash_file = info_file.with_suffix('')
                    
                    if trash_file.exists():
                        stat = trash_file.stat()
                        size = stat.st_size if trash_file.is_file() else None
                        
                        trash_item = TrashItem(
                            name=Path(info_data['original_path']).name,
                            original_path=info_data['original_path'],
                            deleted_at=datetime.fromisoformat(info_data['deleted_at']),
                            size=size,
                            is_dir=info_data.get('is_dir', False)
                        )
                        trash_items.append(trash_item)
                        
                except (json.JSONDecodeError, KeyError, ValueError):
                    continue
                    
        except OSError:
            pass
        
        return trash_items
    
    def restore_from_trash(self, original_path: str, target_path: Optional[str] = None) -> bool:
        """从回收站恢复文件"""
        try:
            trash_path = self._get_trash_path(original_path)
            info_file = trash_path.with_suffix('.info')
            
            if not trash_path.exists() or not info_file.exists():
                return False
            
            # 确定恢复目标路径
            restore_path = target_path or original_path
            full_restore_path = self._get_full_path(restore_path)
            
            # 确保目标目录存在
            full_restore_path.parent.mkdir(parents=True, exist_ok=True)
            
            # 移动文件回原位置
            shutil.move(str(trash_path), str(full_restore_path))
            
            # 删除信息文件
            info_file.unlink()
            
            return True
            
        except Exception:
            return False
    
    def delete_from_trash(self, original_path: str) -> bool:
        """从回收站永久删除文件"""
        try:
            trash_path = self._get_trash_path(original_path)
            info_file = trash_path.with_suffix('.info')
            
            if trash_path.exists():
                if trash_path.is_dir():
                    shutil.rmtree(trash_path)
                else:
                    trash_path.unlink()
            
            if info_file.exists():
                info_file.unlink()
            
            return True
            
        except Exception:
            return False
    
    def empty_trash(self) -> int:
        """清空回收站"""
        if not self.trash_path or not self.trash_path.exists():
            return 0
        
        count = 0
        try:
            for item in self.trash_path.iterdir():
                try:
                    if item.is_dir():
                        shutil.rmtree(item)
                    else:
                        item.unlink()
                    count += 1
                except Exception:
                    continue
        except OSError:
            pass
        
        return count
    
    def get_trash_item_info(self, original_path: str) -> Optional[TrashItem]:
        """获取回收站项目信息"""
        try:
            trash_path = self._get_trash_path(original_path)
            info_file = trash_path.with_suffix('.info')
            
            if not info_file.exists():
                return None
            
            with open(info_file, 'r') as f:
                info_data = json.load(f)
            
            if trash_path.exists():
                stat = trash_path.stat()
                size = stat.st_size if trash_path.is_file() else None
                
                return TrashItem(
                    name=Path(info_data['original_path']).name,
                    original_path=info_data['original_path'],
                    deleted_at=datetime.fromisoformat(info_data['deleted_at']),
                    size=size,
                    is_dir=info_data.get('is_dir', False)
                )
            
        except (json.JSONDecodeError, KeyError, ValueError, OSError):
            pass
        
        return None